﻿#region Copyright(C) 2010-2010 NCS Pte. Ltd. All rights reserved.
// ==================================================================================================
// Copyright(C) 2010 NCS Pte. Ltd. All rights reserved.
//
// SYSTEM NAME	    : iConnect .Net Application Framework
// COMPONENT ID	    : NCS.IConnect.Core.Wcf
// COMPONENT DESC   : Core components for WCF.	
//
// CREATED DATE/BY  : 18 Aug 2010 / Gao Ye
//
// REVISION HISTORY :	
// DATE/BY			SR#/CS/PM#/OTHERS		DESCRIPTION OF CHANGE
// ==================================================================================================
#endregion

using NCS.IConnect.Core.Utilities;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;


namespace NCS.IConnect.Core.IoC.Wcf
{
    /// <summary>
    /// The ServiceBehavior and EndpointBehavor use <see cref="ServiceLocatableInstanceProvider"/> to create service instance.
    /// </summary>
    public class ServiceLocationBehaviorAttribute: Attribute, IServiceBehavior, IEndpointBehavior
    {
        /// <summary>
        /// Gets or sets the service locator.
        /// </summary>
        /// <value>The service locator.</value>
        public IServiceLocator ServiceLocator { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceLocationBehaviorAttribute"/> class.
        /// </summary>
        /// <param name="serviceLocatorName">Name of the service locator.</param>
        public ServiceLocationBehaviorAttribute(string serviceLocatorName)
        {
            Guard.ArgumentNotNullOrEmpty(serviceLocatorName, "serviceLocatorName");
            this.ServiceLocator = ServiceLocatorFactory.GetServiceLocator(serviceLocatorName);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceLocationBehaviorAttribute"/> class.
        /// </summary>
        public ServiceLocationBehaviorAttribute()
        {
            this.ServiceLocator = ServiceLocatorFactory.GetServiceLocator();
        }

        /// <summary>
        /// Provides the ability to pass custom data to binding elements to support the contract implementation.
        /// </summary>
        /// <param name="serviceDescription">The service description of the service.</param>
        /// <param name="serviceHostBase">The host of the service.</param>
        /// <param name="endpoints">The service endpoints.</param>
        /// <param name="bindingParameters">Custom objects to which binding elements have access.</param>
        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }

        /// <summary>
        /// Provides the ability to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects.
        /// </summary>
        /// <param name="serviceDescription">The service description.</param>
        /// <param name="serviceHostBase">The host that is currently being built.</param>
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
            {
                foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
                {
                    Type serviceContractType = GetContractType(serviceDescription, endpointDispatcher);
                    //MEX endpoint.
                    if (null == serviceContractType)
                    {
                        continue;
                    }
                    endpointDispatcher.DispatchRuntime.InstanceProvider = new ServiceLocatableInstanceProvider(this.ServiceLocator, serviceContractType);
                }
            }
        }

        /// <summary>
        /// Provides the ability to inspect the service host and the service description to confirm that the service can run successfully.
        /// </summary>
        /// <param name="serviceDescription">The service description.</param>
        /// <param name="serviceHostBase">The service host that is currently being constructed.</param>
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }

        /// <summary>
        /// Implement to pass data at runtime to bindings to support custom behavior.
        /// </summary>
        /// <param name="endpoint">The endpoint to modify.</param>
        /// <param name="bindingParameters">The objects that binding elements require to support the behavior.</param>
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }

        /// <summary>
        /// Implements a modification or extension of the client across an endpoint.
        /// </summary>
        /// <param name="endpoint">The endpoint that is to be customized.</param>
        /// <param name="clientRuntime">The client runtime to be customized.</param>
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }

        /// <summary>
        /// Implements a modification or extension of the service across an endpoint.
        /// </summary>
        /// <param name="endpoint">The endpoint that exposes the contract.</param>
        /// <param name="endpointDispatcher">The endpoint dispatcher to be modified or extended.</param>
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.InstanceProvider = new ServiceLocatableInstanceProvider(this.ServiceLocator, endpoint.Contract.ContractType);
        }

        /// <summary>
        /// Implement to confirm that the endpoint meets some intended criteria.
        /// </summary>
        /// <param name="endpoint">The endpoint to validate.</param>
        public void Validate(ServiceEndpoint endpoint) { }

        /// <summary>
        /// Gets the type of the contract.
        /// </summary>
        /// <param name="serviceDescription">The service description.</param>
        /// <param name="endpointDispatcher">The endpoint dispatcher.</param>
        /// <returns>The service contract type matching the <see cref="EndpointDispatcher"/>.</returns>
        private Type GetContractType(ServiceDescription serviceDescription, EndpointDispatcher endpointDispatcher)
        {
            var filters = serviceDescription.Endpoints.Where(endpoint => endpoint.Contract.Name == endpointDispatcher.ContractName &&
                endpoint.Contract.Namespace == endpointDispatcher.ContractNamespace);
            if (filters.Count() == 0)
            {
                return null;
            }
            return filters.ToArray<ServiceEndpoint>()[0].Contract.ContractType;
        }
    }
}
